package viz_ui;

import java.awt.Color;
import java.util.HashMap;

import controllers.ControlManager;
import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;
import buttons.Button;
import buttons.CheckBox;
import buttons.DoubleSlider;
import buttons.DropDownList;
import buttons.VerticalDoubleSlider;
import buttons.VerticalDropDownList;
import buttons.control.C_Button;
import buttons.control.C_DropDown;
import buttons.view.V_Button;
import buttons.view.V_DoubleSlider;
import buttons.view.V_DropDown;
import utilities.EventListener;
import utilities.EventOwner;
import view.V_DisplayObject;
import viz_main.ScatterPlotAxis;
import viz_main.ScatterPlotCAxis;
import viz_main.ScatterPlotPoint;
import viz_main.VizApplet;
import viz_main.BarComponent;

public class BarView extends V_DisplayObject{
	
	private VizApplet parent;
	private DropDownList xLabelList;
	private DoubleSlider xFilter;
	private CheckBox scaleToggle;
	private Button[] internalDisplayList;
	
	private ScatterPlotCAxis xAxis;
	private ScatterPlotCAxis yAxis;
	
    private final float PAD = 1; // a pad value (adjust to increase/decrease tightness to the sides
    private final float X_INDENT = 30; // amount of extra indent we add to the x axis
    private final float Y_INDENT = 50; // amount of extra indent we add to the y axis
    
	private PFont labelFont; // used for text labels
	private PFont numFont; // used for numbers
	private int minFntSz = 8;
	private int maxFntSz = 12;
	private int curFntSz;
	
    private float yStart;
    private float yEnd ; 
    private float xStart ;
    private float xEnd ;
    // the max value is used on the yAxis. it is rounded to the nearest integer.
    private int max;		// this max value changes both when axis is swapped and when scaling size is reduced
    private int storedMax;  // this value doesn't change unless the axis is swapped
    private float xInterval;
	
	private BarComponent[] barComps = new BarComponent[0];
	private int filteredStartIndex;
	private int filteredEndIndex;
	
	private int timeDrawCount;
	
	// TODO: tooltip when mouse over one of the bars.
	// 		 we may be able to compute the bar index without having to check the mouse at every bar position. (way more efficient)
	public BarView(float xPos, float yPos, float w, float h, ControlManager c, VizApplet p)
	  {
		this.init(xPos, yPos, w, h, new Color(230, 230, 230, 150));
	    parent = p;
	    // NOTE: this component manages the display of its internal buttons but it sends the update
	    // management for interaction to the controlManager.
	    
	    curFntSz = (minFntSz+maxFntSz)/2;
	    labelFont = p.createFont("Arial-Black-12", 10);
	    numFont = p.createFont("ArialMT-12", minFntSz);
	   
	    //(ScatterPlotAxis)axisList.get(labels[0])
	    xLabelList = new DropDownList(null, c, "xAxis", 120, 12, parent);
	    xLabelList.addOptions(p.labels);
	    //xLabelList.addSubscriber(this);
	    xLabelList.setNameVisible(false);
	    // set the xLabel tooltip:
	    xLabelList.setTooltip(p.getTooltipController(), "adjust category");

	    // add the xSlider that filters content
	    xFilter = new DoubleSlider(null, c, "xFilter", w/4, 12);
	    xFilter.setTooltip(p.getTooltipController(), "control view range");
	    scaleToggle = new CheckBox(null, c, "scale attributes", 12, 12, parent);
	    scaleToggle.setTooltip(p.getTooltipController(), "scale amount to local maximum (when filtered)");
	    internalDisplayList = new Button[]{xLabelList, xFilter, scaleToggle};
	    
	    updatePosition();
	  }
	  
	  public void setXAxis(ScatterPlotCAxis axis)
	  {
	    xAxis = axis;
	    ((V_DropDown)xLabelList.getView()).setCurOption(xAxis.getLabel());
	    ((C_DropDown)xLabelList.getController()).cacheCurOption();
	    
	    updateInterval();
	    
	    //POPULATE BARS
	    // TODO add updateBars here later
	    generateBars();
	  }
	  
	  // Req. non-null axis in order to properly generate the bars:
	  public void generateBars()
	  {
		int number = xAxis.getUniqueCategories().size();
		float curXPos = xStart;
		
		barComps = new BarComponent[number];
	    if(xAxis != null){
	      for(int i = 0; i < number; i++)
	      {
	    	int amount = (xAxis.getCategoryMap().get(xAxis.getUniqueCategories().get(i)).length);
	        barComps[i] = new BarComponent(
	        		(curXPos+xInterval/2)-xInterval/2+(xInterval/10), yStart, // xLoc, yLoc
	        		xInterval-(xInterval/5), (amount*((yStart-yEnd)/max)),  // xSize, ySize
	        		parent.pointColor, amount);	// color, amount
	        curXPos += xInterval;
	       
	      }
	    }
	  }
	  
	  // Req. non-null axis and an already existing array of bars:
	  // toggle visibility of bars based on the current filtered area: 
	  // also update their width to the new xInterval
	  public void updateBars()
	  {
		 float curXPos = xStart;
		 for(int i = 0; i < barComps.length; i++)
		 {
			barComps[i].setVisibility(false);
			// if this bar is in the new filter range...
			if(i >= filteredStartIndex && i < filteredEndIndex)
			{
				barComps[i].setVisibility(true);
				barComps[i].setXSize( xInterval-(xInterval/5));
				barComps[i].setXLoc((curXPos+xInterval/2)-xInterval/2+(xInterval/10));
				curXPos += xInterval;
			}
			else {
				barComps[i].setSelected(false);
			}
		 }
	  }
	  
	  //Updates the interval for the x axis as well as the largest bar height
	  public void updateInterval(){
		    filteredStartIndex = (int) (xAxis.getUniqueCategories().size() * xFilter.getXPercent());
		    filteredEndIndex = (int) (xAxis.getUniqueCategories().size() * xFilter.getYPercent());
		    xInterval = ((xEnd) - xStart)/(filteredEndIndex - filteredStartIndex); // the actual interval
		    
		    max = 0;
		    for(int i = 0; i < xAxis.getCategoryMap().size(); i++){
		    	if(max < xAxis.getCategoryMap().get(xAxis.getUniqueCategories().get(i)).length){
		    		max = xAxis.getCategoryMap().get(xAxis.getUniqueCategories().get(i)).length;
		    	}
		    }
		    storedMax = max;
	  }
	 
	  //Updates the start and end positions 
	  public void updatePosition(){
		  	yStart = yLoc + ySize - curFntSz - PAD - Y_INDENT;
		  	yEnd = yLoc + PAD + 4; 
		  	xStart = xLoc + PAD + X_INDENT + curFntSz;
		    xEnd = xLoc+xSize-8; 
	  }
	  
	  @Override
	  public void display(PApplet p) {
	    p.noStroke();
	    p.fill(bgColor);
	    p.rect(xLoc, yLoc, xSize, ySize);
	    p. fill(0);
    
    /* *
     * DRAW lines and numbers:
     * */
	    p.textFont(numFont);
	    p.stroke(100);
	    p.strokeWeight(1);
	    
	    // draw the axis:
	    p.line(xStart, yStart, xStart, yEnd); // draw vertical line
	    p.line(xStart, yStart, xEnd, yStart); // draw horizontal line
	    
	    int rnd = 100; // round values to hundredths.
	    // based on the width of the GridView and the range of values, calculate distribution:
	    float t = 1.5f; // half the length of the tick line segment
	    
	    // draw ticks for x-Axis:
	    //System.out.println("Check: " + xAxis.getUniqueCategories().size());
	    float inc = xAxis.getUniqueCategories().size(); // the data interval (for text display)
	    //float inc = 50;
	    float curXPos = xStart;
	    float lastXPos = curXPos;
	    float diff = 0;
	    float curInc = 0; // TODO: set to zero later

	    //System.out.print("max1: " + max);
	    //calculates the range of the y-axis
	    if (max%10 != 0){
		    max = max + (10-(max%10));
	    }

	    //System.out.println(" : max2: " + max);
	    
	    //p.text((float)((int)(curInc*10))/10+"", curXPos-(p.textWidth((float)((int)(curInc*10))/10+"")/2), yStart + t + 10);	    
	    
	 // DRAW ticks for y-Axis:
	    inc = (float)max/10; // the data interval (for text display)
	    float yInterval = ((yStart) - yEnd)/10;  // the actual interval
	    float curYPos = yStart;
	    curInc = 0;
	    
	    p.text((((int)(curInc*10))/10)*-1+"", xStart-(p.textWidth((((int)(curInc*10))/10)*-1+""))-t-3, curYPos + minFntSz/2);
	    for(int i = 0; i < 10; i++) {
	      float tmpYPos = curYPos;
	      for(int j = 0; j < 2; j++) // draw 3 sub-ticks:
	      {
	        tmpYPos -= yInterval/2;
	        p.stroke(225);
	        p.line(xStart, tmpYPos, xEnd, tmpYPos);
	        p.stroke(0);
	        p.line(xStart - t, tmpYPos, xStart + t, tmpYPos);
	      }
	      p.strokeWeight(2);
	      curYPos -= yInterval;
	      p.line(xStart - t, curYPos, xStart + t, curYPos);
	      p.strokeWeight(1);
	      curInc -= inc;
	      p.text(( ((int)(curInc*rnd))/rnd)*-1+"", xStart-(p.textWidth(( (float)((int)(curInc*rnd))/rnd)*-1+""))-t+3, curYPos + minFntSz/2);
	    }
	    
	    float mouse = p.mouseX;
	    
	    timeDrawCount = -1;
	    
	    // DRAW X LABELS:
	    // new: draw them filtered
	    for(int i = filteredStartIndex; i < filteredEndIndex; i++) {
		      float tmpXPos = curXPos;
		      p.stroke(128);
		      p.strokeWeight(2);
		      lastXPos = curXPos;
		      curXPos += xInterval;
		      diff = curXPos-lastXPos;
		      
	     	  int amount = (xAxis.getCategoryMap().get(xAxis.getUniqueCategories().get(i)).length);
	     	  float mDiff = -(mouse-curXPos);
	     	  float mDiff2 = Math.abs(mouse-curXPos);
		      
		      // draw each tick:
		      p.line(curXPos-diff/2, yStart + t, curXPos-diff/2, yStart - t);
		      p.strokeWeight(1);
		      curInc += inc;
		      
		      //Rotate and display labels for the x axis (might work better once all the categories are Strings)
		      p.pushMatrix();
		      // TODO: use xIncrement instead of a static value
		      
		      p.textFont(labelFont);
		      
		      //Label Size based on amount of categories and distance from mouth
		      if(mouse > xStart){
			      if(barComps[i].isSelected()){
			    	  p.fill(50,140,150,220);
			    	  p.textSize(9);
			      }
			      else if(mouse >= (curXPos+(xInterval/10)-xInterval)  && mouse <= (curXPos-xInterval/5)){
			    	  p.fill(0);
			    	  p.textSize(9);
			      }
			      else{
			    	  
			    	  p.fill(0,255-(float)(mDiff2/2.7));
			    	  if(9-(mDiff2/120+(float)(xAxis.getUniqueCategories().size()/5)) > 2){
			    		  p.textSize(9-(mDiff2/120+(float)(xAxis.getUniqueCategories().size()/5)));
			    	  }
			    	  else {
			    		  if(4-mDiff2/120 > 2){
			    			  p.textSize(4-mDiff2/120);
			    		  }
			    		  else {
			    			  p.textSize(2);
			    		  }
			    	  }
			      }
		      }
		      else{
		    	  if(barComps[i].isSelected()){
			    	  p.fill(50,140,150,220);
			    	  p.textSize(9);
			      }
		    	  else{
		    		  p.fill(0);
		    		  p.textSize(4);
		    	  }
		      }
	    	  p.translate(curXPos - (xInterval/2), yStart+7);
	    	  p.rotate(1);
	    	  //Draw only every 30 Time x-axis marks
	    	  if (xAxis.getLabel().equals("Time"))
	    	  {
	    		  if(barComps[i].isSelected()){
			    	  p.fill(50,140,150,220);
			    	  p.textSize(9);
		    		  p.text(xAxis.getUniqueCategories().get(i), 20, 10);

			      }
	    		  if (timeDrawCount == 30){
			    	  p.textSize(8);
		    		  p.text(xAxis.getUniqueCategories().get(i), 0, 0);
		    		  timeDrawCount = 0;
	    		  }
	    		  else
	    			 p.text(" ");
	    		  
	    		  timeDrawCount += 1;

	    	  }
	    	  else
	    		  p.text(xAxis.getUniqueCategories().get(i), 0, 0);
	    	  
	    	  p.popMatrix();
		    }
	    updateComponents(p);
	  }
	  
	  public void updateComponents(PApplet p)
	  {
		// draw labels:    
	    // TODO: the position of the dropDown probably doesn't need to be re-calculated every draw call. Only need to do that
	    // when the window is resized or instantiated.
	    xLabelList.getView().setLocation(xLoc + (xSize/2) - (xLabelList.getView().getXSize()/2), (yLoc + ySize - xLabelList.getView().getYSize()-PAD));
	    xFilter.getView().setLocation(xLoc + (xSize/2) + (xLabelList.getView().getXSize()/2) + 20, (yLoc + ySize - xFilter.getView().getYSize()-PAD));
	    scaleToggle.getView().setLocation(xLoc + 80, (yLoc + ySize - xFilter.getView().getYSize()-PAD));
	    // draw buttons:
	    for(int i = 0; i < internalDisplayList.length; i++)
	    {
	    	internalDisplayList[i].display(p);
	    }
	    
	    // draw bars:
	    //float direction = getScaleToggleValue()? 1.0f : 0.0f;
	    //float scalePercent = Math.abs(direction - Math.min(1.0f, ((float)max)/((float)storedMax)));
	    float scalePercent = Math.min(1.0f, ((float)max)/((float)storedMax));
	    //System.out.println(scalePercent);
	    for(int i = 0; i < barComps.length; i++)
	    {
	    	barComps[i].display(parent, scalePercent, yStart-yEnd, yStart);
	    }

	  }
	  
	  public void setMax(int val)
	  {
		  max = val;
	  }
	  public void resetMax()
	  {
		  max = storedMax;
	  }
	  
	  public float getXInterval() {
		  return xInterval;
	  }
	  
	  public float getXStart() {
		  return xStart;
	  }
	  public float getXEnd() {
		  return xEnd;
	  }	  
	  
	  
	  public float getYStart() {
		  return yStart;
	  }
	  public float getYEnd() {
		  return yEnd;
	  }	  
	  public int getFilteredStartIndex(){
		  return filteredStartIndex;
	  }
	  
	  
	  public BarComponent[] getAllBars()
	  {
		  return barComps;
	  }
	  
	  public Button[] getButtons()
	  {
		  return internalDisplayList;
	  }
	  
	  public DoubleSlider getXFilter()
	  {
		  return xFilter;
	  }
	  
	  public boolean getScaleToggleValue()
	  {
		  return scaleToggle.getToggle();
	  }
	  
	  public ScatterPlotCAxis getXAxis()
	  {
		  return xAxis;
	  }
	  
	  public int getFilteredArrayLength()
	  {
		  return filteredEndIndex - filteredStartIndex;
	  }
	  
	  public int[] getFilteredIndexes()
	  {
		  return new int[] {filteredStartIndex, filteredEndIndex};
	  }
	  
	  // utility that allows the controller to access the list of axis while maintaining encapsulation
	  public HashMap<String, ScatterPlotCAxis> getCAxisList()
	  {
		  return parent.getCAxisList();
	  }
}
